﻿using System.Collections.Concurrent;
using Devonline.Entity;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Timer = System.Timers.Timer;

namespace Devonline.Communication.Server;

/// <summary>
/// 消息可持久化保存的服务器
/// </summary>
public class PersistentMessageServer : MessageServer
{
    /// <summary>
    /// 计时器是否已初始化
    /// </summary>
    protected static bool _initiated = false;
    /// <summary>
    /// 定时器
    /// </summary>
    protected static Timer _timer = new();
    /// <summary>
    /// 消息缓存, 因为写数据库速度太慢, 因此读写分离
    /// </summary>
    protected static ConcurrentDictionary<string, Message> _messages = new();
    /// <summary>
    /// 数据库上下文, 此时需要注入单例
    /// </summary>
    protected readonly ApplicationDbContext? _context;
    public PersistentMessageServer(
        ILogger<PersistentMessageServer> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        HostSetting hostSetting,
        ApplicationDbContext messageContext
        ) : base(logger, cache, httpContextAccessor, hostSetting)
    {
        _context = messageContext;
        if (!_initiated)
        {
            InitTimer();
        }
    }

    /// <summary>
    /// 消息持久化保存方式
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    protected override async Task SaveMessageAsync(Message message)
    {
        await base.SaveMessageAsync(message);
        if (_messages.TryGetValue(message.Id, out Message? originalMessage))
        {
            _messages.TryUpdate(message.Id, message.CopyTo(originalMessage), originalMessage);
        }
        else
        {
            _messages.TryAdd(message.Id, message);
        }

        if (!_timer.Enabled)
        {
            _logger.LogDebug($"The Server has beed received the message so the timer start now!");
            _timer.Start();
        }

        _logger.LogDebug($"Server will save the {message.Type} message to database later which from sender {message.Sender} in client {message.From} to receiver {message.Receiver} with message id: {message.Id}");
    }

    /// <summary>
    /// 初始化计时器
    /// </summary>
    private void InitTimer()
    {
        _logger.LogInformation("The Server will initial the timer!");
        lock (_messages)
        {
            if (!_initiated)
            {
                _initiated = true;
                _timer = new Timer
                {
                    AutoReset = true,
                    Interval = _hostSetting.Execute.MonitorInterval * AppSettings.UNIT_THOUSAND
                };

                _logger.LogWarning("The Server start timer to write messages to database!");
                _timer.Elapsed += async (sender, args) => await WriteToDatabaseAsync();
            }
        }
    }
    /// <summary>
    /// 保存到数据库
    /// </summary>
    /// <returns></returns>
    private async Task WriteToDatabaseAsync()
    {
        _logger.LogDebug($"The Server will write messages to database!");
        var messages = _messages.GetFromCollection();
        if (messages is null || messages.Count == 0 || _context == null || _context.Messages is null)
        {
            _logger.LogDebug($"The Server has no messages so the timer will stop moment!");
            _timer.Stop();
            return;
        }

        try
        {
            _logger.LogDebug($"The Server will save {messages.Count} count of messages in to database!");
            var exists = await _context.Messages.AsNoTracking().Where(x => messages.Select(a => a.Key).Contains(x.Id)).Select(x => x.Id).ToListAsync();
            var inserts = messages.Where(x => !exists.Any(a => a == x.Key)).Select(x => x.Value);
            if (inserts.Any())
            {
                _context.Messages.AddRange(inserts);
            }

            var updates = messages.Where(x => exists.Any(a => a == x.Key)).Select(x => x.Value);
            if (updates.Any())
            {
                _context.Messages.UpdateRange(updates);
            }

            var result = await _context.SaveChangesAsync();
            _logger.LogInformation($"The Server has been saved {result} count in {messages.Count} total messages to database!");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"The Server write {messages.Count} messages to database error, will retry later!");
            lock (_messages)
            {
                _messages.AddRange(messages);
            }
        }
    }
}